package com.jiunuan.live.core.server.handler.impl;

import com.alibaba.fastjson2.JSON;
import com.jiunuan.live.core.server.common.ImContextUtils;
import com.jiunuan.live.core.server.common.ImMsg;
import com.jiunuan.live.core.server.handler.SimplyHandler;
import com.jiunuan.live.framework.redis.starter.key.IMCoreServerProviderCacheKeyBuilder;
import com.jiunuan.live.im.constants.ImConstants;
import com.jiunuan.live.im.constants.ImMsgCodeEnum;
import com.jiunuan.live.im.core.server.interfaces.constants.ImCoreServerConstants;
import com.jiunuan.live.im.dto.ImMsgBody;
import io.netty.channel.ChannelHandlerContext;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @Author: JiuNuan
 * @CreateTime: 2024-08-10 21:52
 * @Description: 心跳消息处理器
 */
@Component
@Slf4j
public class HeartBeatImMsgHandler implements SimplyHandler {
    @Resource
    private RedisTemplate<String,Object> redisTemplate;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private IMCoreServerProviderCacheKeyBuilder cacheKeyBuilder;
    /**
     * 消息处理函数，根据code匹配不同实现类
     *
     * @param ctx
     * @param imMsg
     */
    @Override
    public void handler(ChannelHandlerContext ctx, ImMsg imMsg) {
        // 心跳包基本校验
        Long userId = ImContextUtils.getUserId(ctx);
        Integer appId = ImContextUtils.getAppId(ctx);
        if(userId == null || appId == null){
            // 可能是错误的消息包导致的，直接放弃连接
            log.error("[HeartBeatImMsgHandler] attr error,imMsg: {}", imMsg);
            ctx.close();
            throw new RuntimeException("[HeartBeatImMsgHandler] attr error");
        }
        // 心跳包record记录，redis存储心跳记录
        // zSet集合存储心跳记录，基于userId MOD 取模， key(userId) - score(心跳时间)
        String redisKey = cacheKeyBuilder.buildIMOnlineZSet(userId,appId);
        this.recordOnlineTime(userId,redisKey);
        this.removeExpiredOnlineTime(userId,redisKey);
        // 更新redisKey的过期时间，5分钟
        redisTemplate.expire(redisKey,5, TimeUnit.MINUTES);
        stringRedisTemplate.expire(cacheKeyBuilder.buildIMBindIpKey(appId,userId),ImConstants.DEFAULT_HEART_BEAT_GAP*2,TimeUnit.SECONDS);
        // 回写心跳包到客户端
        ImMsgBody imMsgBody = new ImMsgBody();
        imMsgBody.setUserId(userId);
        imMsgBody.setAppId(appId);
        imMsgBody.setData("true");
        ImMsg respMsg = ImMsg.build(ImMsgCodeEnum.IM_HEARTBEAT_MSG.getCode(), JSON.toJSONString(imMsgBody));
        log.info("[HeartBeatImMsgHandler] imMsg:{}",imMsg);
        ctx.writeAndFlush(respMsg);
    }

    /**
     * 移除过期的在线时长(在两次心跳包的发送间隔中，如果没有重新更新score值，就会导致被删除)
     * 本方法用于从Redis中移除超过指定时间范围的在线时长记录
     * 主要用于清理长时间未活跃的用户在线时长数据，以确保数据的准确性和实时性
     *
     * @param userId 用户ID，用于区分不同的用户
     * @param redisKey Redis中的键，用于定位到特定的存储位置
     */
    private void removeExpiredOnlineTime(Long userId, String redisKey) {
        // 使用redisTemplate的操作forZSet方法来操作Redis中的有序集合
        // removeRangeByScore方法根据给定的分数范围删除集合中的元素
        // 参数0表示开始分数，System.currentTimeMillis() - ImConstants.DEFAULT_HEART_BEAT_GAP*1000*2表示结束分数，
        // 即当前时间戳减去最长心跳间隔时间，计算方式为DEFAULT_HEART_BEAT_GAP(心跳间隔时间)*1000(转换为毫秒)*2(加倍，确保覆盖到所有过期数据)
        // 这样可以确保所有超过最长心跳间隔时间的数据都会被删除
        redisTemplate.opsForZSet().removeRangeByScore(redisKey,0,
                System.currentTimeMillis() - ImConstants.DEFAULT_HEART_BEAT_GAP*1000*2);
    }

    /**
     * 记录用户最近一次心跳时间在zSet上
     * @param userId
     * @param redisKey
     */
    private void recordOnlineTime(Long userId,String redisKey){
        redisTemplate.opsForZSet().add(redisKey,userId,System.currentTimeMillis());
    }
}
